package es.abredatos.energyes

import java.util.zip.ZipFile

class DataService {

	def grailsApplication
	def dateService
	/**
	 * This mehtod tries to get and import the data file whose month
	 * and year is equals to the month&date of the passed date.
	 * 
	 * If a file for this month&year has been processed previously, this method
	 * doesn't make the request and exists quietly.
	 */
	void getAndImportMonthlyData(Date date) {
		Integer monthAsInteger, year
		String monthAsString
		def mapFiles
		
		monthAsString = dateService.getMonthAsString(date)
		monthAsInteger = date[Calendar.MONTH]
		year = date[Calendar.YEAR]
		log.debug("Date: ${monthAsInteger} - ${year}")
		if(!isProcessedFile(monthAsInteger, year)) {
			log.debug("Getting data from the network...")
			mapFiles = getMapFiles(monthAsString, year)
			
			log.debug("Received data: ${mapFiles}")
			
			if(mapFiles) {
				parseFiles(mapFiles[FileTypeEnum.GLOBAL], mapFiles[FileTypeEnum.THERMAL],
					mapFiles[FileTypeEnum.COMMERCIAL], monthAsInteger, year)
				
				log.debug("Setting file as processed...")
				setFileAsProcessed(monthAsInteger, year)
			}
		}
		
		log.debug("Get and import done!")
	}
	
	/**
	 * Returns a map whose key is the File Type and whose value is the text of the file,
	 * or null if anything fails for given month and year
	 * 
	 * @param month the month's name, in spanish
	 * @param year the year (2010, 2011...)
	 */
	private Map getMapFiles(String month, Integer year) {
		def mapFiles = [:]
		def zipUrl = getUrlFor(month, year) //a URL object
		if (zipUrl) {
			def tempFile = File.createTempFile("enery", null)
			tempFile.deleteOnExit() //will be deleted when the JVM is terminated
			zipUrl.withInputStream { is ->
				tempFile << is
			}
			log.debug("ZIP file downloaded as ${tempFile.name}")
			
			def zipFile = new ZipFile(tempFile)
			zipFile.entries().each { entry ->
				 if (!entry.isDirectory()) {
					 log.debug "Reading ${entry.name}"
					 def fileName = entry.name.replaceFirst("^.*\\/", "")
					 if(FileTypeEnum.isValidFileType(fileName)){
						 def fileText = zipFile.getInputStream(entry).text 
						 mapFiles[FileTypeEnum.getFileType(fileName)] = fileText
					 }
				 }
			}
			zipFile.close()
			return mapFiles
		} else return null
	}
	
	/**
	 * Finds URL for given month and year, looking at the response code.
	 * If it is a valid one, returns the opened URL object
	 * 
	 * @param month Month as name
	 * @param year Year as integer 
	 */
	private URL getUrlFor(month, year) {
		def prefix = "${grailsApplication.config.energy.data.url}${year}"
		def url = "${prefix}/${month}_${year}.zip"
		def connection = openUrl(url)
		if (!connection) {
			url = "${prefix}/${month}%20${year}.zip"
			connection = openUrl(url)		
		}
		
		log.debug("URL for ZIP downloading is: ${url}")
		
		return connection
	}
	
	/**
	 * Checks wether a URL has a 200 or 201 HTTP status code. If so, returns
	 * the URL object, else null
	 * 
	 * @param url URL as String
	 */
	private def openUrl(url) {
		def connection = new URL(url).openConnection()
		connection.connect()
		if (connection.responseCode in [200,201]) return connection.getURL()
		else return null
	}
	
	
	/**
	 * This method will parse the content of the three parameters and it will
	 * create the right Production objects associated with the read data.
	 * 
	 * @param globalEnergies Data about the production of these energies: 
	 * 			HIDROELECTRICA, EOLICA and SOLAR (file: T_125P_MMYY.txt)
	 * 
	 * @param thermoEnergies Data about the production of these energies:
	 * 			NUCLEAR, CARBON, LIGNITOS, FUEL, GAS_NATURAL, OTROS
	 * 			(file: T_127P_MMYY.txt)
	 * 
	 * @param commercial Data about the global production and the 
	 * 					import/export ratio (file: T_121_MMYY.txt)
	 * 
	 * @param month The month associated with the data (0-11)
	 * @param year The year associated with the data
	 */
	private void parseFiles(String globalEnergies, String thermoEnergies, String commercial, Integer month, Integer year) {
		def positions
		
		if(globalEnergies) {
			positions = grailsApplication.config.energy.data.columnDistribution.global
			parseProductionData(globalEnergies, positions, month, year)
		}
		
		if(thermoEnergies) {
			positions = grailsApplication.config.energy.data.columnDistribution.thermo
			parseProductionData(thermoEnergies, positions, month, year)
		}
		
		if(commercial) {
			parseCommercialData(commercial, month, year)
		}
	}
	
	/**
	 * This method will parse the info from the commercial file: T_121_MMYY.txt
	 * 
	 * @param data The file content.
	 */
	private void parseCommercialData(String data, Integer month, Integer year) {
		log.debug("Parsing the commercial content...")
		// TODO Hacer esto
	}
	
	/**
	 * This method will iterate over the data passed in 'energiesData' and it will
	 * parse each line considering the distribution of energies passed in 'positions'.
	 * 
	 * It will consider only the first data line associated with each province.
	 */
	private void parseProductionData(String energiesData, List positions, Integer month, Integer year) {
		def dataAsArray
		def line, province
		def provinces = Province.list()
		def processedProvinces = []
		
		energiesData.eachLine { currentLine ->
			
			province = provinces.find{currentLine.contains(it.code)}
			
			// We consider only the lines with provinces...
			if(province) {
			
				log.debug("Parsing the line for the province ${province}: ${currentLine}...")
				
				// Removing the code part from the line to make easier the split process
				currentLine -= province.code
				dataAsArray = currentLine.trim().split()*.trim()
				
				log.debug("Data as array: ${dataAsArray}")
				
				if(!processedProvinces.contains(province)) {
					log.debug("The data must be processed")
					
					processedProvinces << province
					parseProductionDataLine(dataAsArray, province, positions, month, year)
				}
			}
		}
	}
	
	/**
	 * This closure must interpret the data array passed as parameter.
	 * This data array has the info of a line from the energy file.
	 */
	private def parseProductionDataLine(List dataAsArray, Province province, List positions, Integer month, Integer year) {
		String productionAsString
		Integer productionAsInteger
		
		positions.each { position ->
			productionAsString = dataAsArray[position[1]].replaceAll('\\.', '') // Removing all the dots
			productionAsInteger = productionAsString as Integer
			
			log.debug("Production data for ${province} (${position[0]}, ${month}/${year}): ${productionAsInteger}")
			saveProductionItem(position[0], province, month, year, productionAsInteger)
		}
	}
	
	/**
	 * This method tries to save a new ProductionItem with the passed parameters.
	 */
	private void saveProductionItem(Energy energy, Province province, Integer month, Integer year, Integer production) {
		ProductionItem p = new ProductionItem(energy:energy, province:province, 
			month:month, year:year, production:production)
		
		log.debug("Saving a new production item: ${p.energy}, ${p.province}, ${p.month}, ${p.year}, ${p.production}")
		
		if(!p.save()) {
			log.error("Error saving the production item: ${p}")
			p.errors.each{log.error(it)}
		}
	}
	
	/**
	 * This method returns true if a ProcessedFile object exists in the database
	 * with the month and year equals to the parameters, and false otherwise.
	 */
	private boolean isProcessedFile(Integer month, Integer year) {
		return ProcessedFile.findByMonthAndYear(month, year) != null
	}
	
	/**
	 * This method creates a new ProcessedFile object with the passed parameter,
	 * and it persists it.
	 */
	private void setFileAsProcessed(Integer month, Integer year) {
		ProcessedFile p = new ProcessedFile(month:month, year:year)
		if(!p.save()) {
			log.error("Error saving the ProcessedFile ${p}")
			p.errors.each{log.error(it)}
		}
	}
	
	void importDataForInterval(List years, List months){
		def allMonths = grailsApplication.config.energy.data.initLoad.months
		def initYear = years[0]
		def endYear = years[years.size()-1]
		def initMonth = months[0]
		def endMonth = months[months.size()-1]
		def intervalMonths = []
		println "InitYear: ${initYear}, EndYear: ${endYear}, InitMonth: ${initMonth}, EndMonth: ${endMonth}"
		log.debug("InitYear: ${initYear}, EndYear: ${endYear}, InitMonth: ${initMonth}, EndMonth: ${endMonth}")
		years.each{ y ->
			if(years.size() > 1 && y==initYear){
				intervalMonths = allMonths[allMonths.indexOf(initMonth)..11]
				log.debug("Months of the first year: ${intervalMonths}")
			}else if(years.size() > 1 && y==endYear){
				intervalMonths = allMonths[0..allMonths.indexOf(endMonth)]
				log.debug("Months of the last year: ${intervalMonths}")
			}else if(years.size() == 1){
					intervalMonths = [*initMonth..endMonth]
			}else intervalMonths=allMonths
			
			intervalMonths.each { m ->
				Calendar calendar = new GregorianCalendar(y,m,1)
				Date date = calendar.time
				getAndImportMonthlyData(date)
			}
		}
	}
	
}
